パソコン活用研究ラピュタへの道(アセンブラ、DOS、Windows、旧型PCの活用研究)
DEBUGで機械語プログラムその2
その1ではHELLOを表示するプログラムを作成してみました。そろそろアセンブラを使いたいところ
ですが、もう一回だけ簡単なプログラムをDEBUGで作成してみることにします。
1 キャラクタの表示
いわゆるアスキーコードの0から255までのキャラクタ(文字)をダッーと表示し続けるプログラムを
作ってみます。何かキーを押すと終了するようにします。
アスキーコードはBLレジスタに持たせます。
C:\newgame\ASM>debug -a100 25AB:0100 mov bl,0 25AB:0102 mov dl,bl 25AB:0104 mov ah,2 25AB:0106 int 21 25AB:0108 mov ah,6 25AB:010A mov dl,ff 25AB:010C int 21 25AB:010E jnz 114 25AB:0110 inc bl 25AB:0112 jmp 102 25AB:0114 int 20 25AB:0116 - -r bx BX 0000 :0000 -r cx CX 0000 :16 -n dispchr.com -w 00016 バイト書き込み中 -q C:\newgame\ASM>dispchr !"#$%&'()*+,-./0123456789:;<=>?@ ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^ _`abcdefghijklmnopqrstuvwxyz{|}~&127?≠ヤ ・炎旧 克署葬灯楓利劒屆撼泛。「」、・ヲァィゥェォャュョ ッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミム メモヤユヨラリルレロワン゙゚珮粤蒟跚韜・・@ ・? C:\newgame\ASM>debug dispchr.com -u 25CD:0100 B300 MOV BL,00 25CD:0102 8AD3 MOV DL,BL 25CD:0104 B402 MOV AH,02 25CD:0106 CD21 INT 21 25CD:0108 B406 MOV AH,06 25CD:010A B2FF MOV DL,FF 25CD:010C CD21 INT 21 25CD:010E 7504 JNZ 0114 25CD:0110 FEC3 INC BL 25CD:0112 EBEE JMP 0102 25CD:0114 CD20 INT 20 |
Debug起動 100番地からアセンブル BLに0をセット(初期値) MS-DOSファンクションコール2番 (DLレジスタの 文字を画面に表示する) ファンクションコール6番(DLがFFHの時はキーボード からの入力。ALにキーコードが渡される) キー入力あれば114番地(終了)へジャンプ BL=BL+1 (キー入力無ければ)102番へジャンプ プログラム終了 BX プログラムサイズのセット プログラム名セット 書き込み Debug終了 dispchrの実行 キャラクタの表示 (DOS窓で実行させた結果は漢字の部分は少し 違う字が表示されるかもしれない) 逆アセンブルしてみた 実際の機械語コードは7504(4byte飛ぶ->114番地へ) 実際の機械語コードはEBEE(EE=-18 18byte 戻る->102番地へ) |
短いコードなので、打ち込んで試してみて下さい。
機械語はなじみがないという方のために、簡単に説明をしておきます。
(1) 機械語の説明
ジャンプ命令
プログラムの実行番地を他の番地にジャンプさせるのがジャンプ命令です。
ジャンプ命令は無条件ジャンプ(JMP)と条件ジャンプに大別できます。
JMPは、BasicでいうとGOTOのようなもんでしょうか。有無を言わさず指定番地に飛ばします。
条件ジャンプには各種ありますが、ある条件を満たした時だけ、ジャンプします。条件が満たされなければ
次の機械語が実行されます。
条件ジャンプを使うことにより、いろんな条件の判定結果によりプログラムを分岐させることができます。
Basicでいうところの If ... Then ... Else みたいなものです。
JMP
JMP コードラベル | コードラベルで示される番地へジャンプ |
アセンブラで記述する時はコードラベルを使いますが、DEBUGで作成する時は、飛び先のオフセットアドレス
を記述します。 (例) JMP 135
JNZ
JNZ コードラベル(番地) | ゼロフラグが0の時、コードラベルで示される番地へジャンプ |
JNZはJump if not Zero の略だと思います。
ゼロフラグは8086の基礎で書きましたが、演算の結果が0、あるいは比較の結果等しい時にセット(=1)
されるフラグです。
ですから、JNZは演算の結果が0でない、比較の結果等しくない時にジャンプする条件ジャンプ命令
ということになります。
このプログラムでは、演算も比較もしていませんが、ファンクションコール6番でキー入力をすると、
キーが押されて入れば、ゼロフラグリセット(=0)、押されて無ければセット(=1)されますので、
それを分岐の条件にしています。すなわち、キー入力があれば114番地にジャンプして、INT
20Hで
プログラム終了、キー入力なければジャンプせず、BLレジスタの値を1つ増加して、102番地へ戻る、
ということになります。
その他の条件ジャンプについては、後で説明します。
INC
INC オペランド(汎用レジスタ,メモリー) | オペランド(汎用レジスタ、メモリー)の値を1増加 |
Increment(増加)の略
DEBUGでの記述例 INC AX AXレジスタの値を1増加
INC [150] 150番地の値を1増加
逆の命令でDECがあります
(2) ファンクションコール
今回は2番と6番を使いました。
2番
AHレジスタに2をセットしてINT 21H。 1文字を画面に出力します。
DLレジスタの文字(アスキーコード)が画面に出力されます。
6番
AHレジスタに6、DLレジスタにFFHをセットしてINT 21H。キーボードからの入力です。
ALレジスタにキーボードからの入力値(アスキーコード)が入ります。入力が無ければNULL(=0)が入ります。
このファンクションコールはBASICで言うとInkey$に近いものです。
ファンクションコール(システムコール)については、別の機会にまとめたいと思います。
(3)補足
INC での桁上がり
INC BL でBLレジスタをひたすら増加させていますが、BLレジスタは8bitですから、FFH(=255)まで
しか数値をとれません。FFHの次は、CF(キャリーフラグ)がセットされて0にもどります。
CFはこのように、桁上がりが生じた時にセットされるフラグです。
アスキーコード
アスキーコードは文字を表すための1Byteのコードです。1byteですから、当然256文字分しかありません。
ということは、もちろん漢字はありません。主要なコードを下記に表にしましたので、参考にして下さい。
ところが、このプログラムを実行すると一部漢字が出てきます。この理由については別の機会に説明
することもあると思いますので、今回は省略します。
試しにDOS窓でUSと打ち込んで、英語モードでこのプログラムを実行してみて下さい。英語モードでは
漢字の変りに他の文字や記号が出るはずです。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | ||
0 | 0 | @ | P | p | ||||||||
1 | ! | 1 | A | Q | a | q | ||||||
2 | " | 2 | B | R | b | r | ||||||
3 | # | 3 | C | S | c | s | ||||||
4 | $ | 4 | D | T | d | t | ||||||
5 | % | 5 | E | U | e | u | ||||||
6 | & | 6 | F | V | f | v | ||||||
7 | ' | 7 | G | W | g | w | ||||||
8 | ( | 8 | H | X | h | x | ||||||
9 | ) | 9 | I | Y | i | y | ||||||
A | LF | * | : | J | Z | j | z | |||||
B | + | ; | K | [ | k | { | ||||||
C | , | < | L | \ | l | |||||||
D | CR | - | = | M | ] | m | } | |||||
E | . | > | N | ^ | n | |||||||
F | / | ? | O | _ | o |
世界的に共通なのはだいたい上で書いた部分だと思います。
0から1FHまでは、制御文字で画面には表示されません。主にプリンタなどの外部機器および端末の制御に
使うために定められた制御用のコードです。パソコンの機種にもよりますが、画面の制御にも使えます。
例えば、0AHはラインフィード(LF)、0DHはキャリジリターン(CR)といい、組み合わせて使うことにより
改行します(MS-DOSの場合)。
80H以上は各国あるいは、機種によりまちまちな部分です。日本ではJISで、A0HからDFHまで半角の
カタカナが割り振られています。
ついでに補足しておくと、漢字は2byteでコードが決められています。JIS漢字コード、シフトJIS漢字コード
などが制定されています。
ハンドアセンブルとジャンプ命令
その昔、8bitパソコンの時代、アセンブラなど容易に入手できなかった時代。BASICから機械語を呼び
出すプログラムを作る時、機械語の部分は手作業で、コードを書いた。手でコーディングしたので、
ハンドアセンブルと言った。
ニモニックを紙の上に書いて、ニモニックと機械語コード対応表をみながら、1行づつ機械語コード
に置き換えた。面倒なのはジャンプ命令であった。アセンブラやDEBUGで作る時は、ラベルを使ったり
飛び先のアドレスを書けばよいのだが、機械語コードでは、現在のIPレジスタ(次に実行する
コードのアドレスをさしている)からどれだけ飛ぶか、という距離をバイト数で記述する。この距離の計算は
まことに面倒なものであった。全てのニモニックを機械語コードに置き換えないと、何バイト離れているか
計算できないのだから、常に頭の痛い問題だった。
って、書いてもわかりにくいと思うけど、逆アセンブラのジャンプ命令のとこ見てください。
ま、どうでもいい問題です。
(4) BASICで記述
これと同じ事をするプログラムをBASICで組むと以下の通り
N88(互換)BASIC用
10 for i=0 to 255
20 print chr$(i);
30 a$=inkey$
40 if a$<>"" then goto 100
50 next
60 goto 10
100 end
2 アスキーコードの表示
ついでに、もうひとつだけDEBUGで簡単な機械語プログラムを作成して、おしまいとしましょう。
今度は先のとは逆で、押されたキーの(文字の)アスキーコードを表示するプログラムです。
これは、16進の数値を画面表示用に変換するルーチン(バイナリデータの画面表示=バイナリデータ−>
テキストデータ変換)を基本とするプログラムです。機械語のプログラムでは必ず必要となるものなので、
アセンブラの入門書では、必ずサンプルコードが載っているはずです。
もちろん「はじめて読む8086」にもサンプルコードが載っていますが、まったく同じコードをのせたのでは
つまらないので、わざわざちょっとコードを変えてみました。
(1) プログラム概要(16進数値変換について)
上のアスキーコード表を見てください。”A”のアスキーコードは41Hです。
”A”のキーを押したらそのアスキーコードの41を表示するプログラムが作成したいものです。
”A”というキーをおすとパソコンには41(16進)が押されたキーのコードとして取り込まれます。
画面に41と表示するには、どうしたらいいでしょうか。
”4”はアスキーコードの34Hです。”1”は31Hです。従って、”41”を画面表示するためには、
アスキーコード34Hと31Hを出力すればよいわけです。さらにこのプログラムは16進数で表示させます
のでアスキーコード41Hから46H(”A”から”F”)までも使います。
(2) プログラム作成
このプログラムは[Ctrl]+"C"で終了してください。
-a100 25CD:0100 MOV AH,01 25CD:0102 INT 21 25CD:0104 MOV DL,20 25CD:0106 CALL 0131 25CD:0109 MOV CL,04 25CD:010B MOV DL,AL 25CD:010D SHR DL,CL 25CD:010F CALL 0123 25CD:0112 MOV DL,AL 25CD:0114 AND DL,0F 25CD:0117 CALL 0123 25CD:011A MOV AH,09 25CD:011C MOV DX,0138 25CD:011F INT 21 25CD:0121 JMP 0100 25CD:0123 CMP DL,09 25CD:0126 JG 012E 25CD:0128 ADD DL,30 25CD:012B JMP 0131 25CD:012D NOP 25CD:012E ADD DL,37 25CD:0131 PUSH AX 25CD:0132 MOV AH,02 25CD:0134 INT 21 25CD:0136 POP AX 25CD:0137 RET 25CD:0138 DB D,A,'$' 25CD:013B -r bx BX 0000 :0000 -r cx CX 0000 :13b -n dispkycd.com -w 0013B バイト書き込み中 -q C:\newgame\ASM>dispkycd 3 33 4 34 5 35 t 74 y 79 u 75 X 58 x 78 : 3A * 2A n 6E 20 ^C |
アセンブル開始 COMファイルにするので100番から ファンクションコール1番 キーボードから1文字入力 DLレジスタに20H(空白文字)をセット 1文字画面表示ルーチンをコール CLレジスタに4をセット。右シフトの回数 DL <--AL ALには押されたキーコードが入っている。まず上位桁の表示 4bit右シフト 上位4bitを下位4bitにシフトした 表示ルーチンコール DL <-- AL ALには押されたキーコードが入っている 下位桁の表示 上位4Bitマスク。 表示ルーチンコール 改行 最初に戻る DLレジスタの値が9以下なら 12E番地へ DL <-- DL+30H DL <-- DL + 37H ファンクションコール2番 1文字画面表示 プログラムサイズをCXにセット プログラム名設定 書き込み 実行してみた スペースキー Ctrl + Cで終了 |
(3)機械語(ニモニック)解説
CALL
CALL コードラベル(番地) | コードラベルで指定される番地をコールする |
BASICでいうところのGosubのようなもの。
DEBUGでは call [230] のようにコールするオフセットアドレスを記述する。
RET
RET | callで呼ばれたサブルーチンからリターンする |
BASICでいうところのReturnのようなもの
演算命令
今回は加算命令のADDを使いました。
ADD
ADD 第1オペランド, 第2オペランド | 第1オペランド<−第1オペランド + 第2オペランド |
R レジスタ M メモリ | DEBUGでの記述例 | |
ADD R, R | ADD AX, DX | AX <-- AX +DX |
ADD M, R | ADD [120], DL | 120番地<−[120]+D [120]は120番地の値 |
ADD R, M | ADD DL, [SI] | DL <--DL+[SI] [SI]はSIレジスタの指す番地の値 |
ADD R, 即値 | ADD AX, FF | AX <-- AX +FFH(=255) |
ADD M, 即値 | ADD Byte ptr[DI],FF | [DI]<--[DI]+ FFH(=255) |
まったく同様に減算命令でSUBというのがあります。
byte ptr について
加算命令は1byteデータ同士の場合と、2byteデータ同士の場合があります。レジスタが使われる場合は
1byteの演算か2byteの演算か自動的に判断されますが、最後の例のように、メモリと即値の演算の場合、
どちらかわかりません。そこで、このような時はメモリーの前にbyte ptr と記述することにより1byteの
メモリーを対象とすることを明示します。2byteの時はword ptrと記述します。
ADD,SUB命令の結果によりOF, SF, ZF, AF, PF, CFが変化します
論理演算命令
論理和 OR や 論理積 ANDの命令です。
AND
AND 第1オペランド,第2オペランド | 第1オペランドと第2オペランドで書くビット枚にANDをとり 結果は第1オペランドに格納する |
R 汎用レジスタ M メモリー | DEBUGでの記述例 | |
AND R,R | AND AX, BX | AXとBXのANDをAXに格納 |
AND M, R | AND [234], BX | |
AND R, M | AND AL, [DI] | |
AND R, 即値 | AND AL, 0F | |
AND M, 即値 | AND byte ptr [130], F0 |
論理和は同様に OR を使います。
今回のプログラムでは、AND DL, 0F でDLレジスタの上位4bit(上位桁)をマスクして、下位4Bitだけを
とりだしています。
例) 11001001
AND 00001111 (=0F)
結果 00001001
AND,ORの結果により、SF, ZF, PFが変化します。CFはレセット(=0)されます
シフト、ローテート命令
SHR
ビットを右シフトし、最上位ビットには0が入る、右シフト命令
SHR 第1オペランド,第2オペランド | 第1オペランド 0−>[最上位Bit 最下位bit]−>CF 第2オペランドはシフトするbit数 |
8086CPUでは第2オペランドは1、またはCLレジスタしか使えない。1以上シフトする時はCLに値を
セットして使う
R 汎用レジスタ M メモリ | DEBUGでの記述例 | ||
SHR R, 1またはCL | SHR BX, 1 | BXレジスタを1bit右シフト | |
SHR M, 1またはCL | SHR byte ptr[240], CL | 240番地の値をCL分右シフト |
例えば ALレジスタに15H(=00010101)がセットされている時、
SHR AL, 1 とすると
ALは0AH(=00001010)になる。
同様に以下のようなシフト、ローテート命令がある
RCL | キャリーを含めた左回転 CF<−−[最上位Bit <−最下位bit]<−− |−−−−−−−−−−−−−−−−−| |
RCR | キャリーを含めた右回転 −>[最上位Bit−> 最下位bit]−−>CF |−−−−−−−−−−−−−−−−−| |
ROL | 左回転 CF<−−[最上位bit<− 最下位bit]<− |−−−−−−−−−−−−| |
ROR | 右回転 −>[最上位Bit−> 最下位bit]−−>CF |−−−−−−−−−−−| |
SAL | 左シフト 最下位Bitには0が入る CF<−−[最上位bit <− 最下位bit]<−0 |
SAR | 右シフト。最上位bitはそのまま [最上位bit−> 最下位bit]−>CF |
CMP
比較命令
CMP 第1オペランド, 第2オペランド | 第1オペランドと第2オペランドの比較をする。 各オペランドの値は変わらないが、内部的には 第1オペランド − 第2オペランド という演算をしている 結果によりOF,SF,ZF,AF,PF,CFが変化する |
R 汎用レジスタ M メモリ | DEBUGでの記述例 | |
CMP R, R | CMP DL, AL | DL, ALの比較 |
CMP M, R | CMP [BX], DL | |
CMP R, M | CMP AX, [1200] | |
CMP R, 即値 | CMP AL, 90 | |
CMP M, 即値 | CMP word ptr[234], 1FFF |
条件ジャンプ
ADD,SUBといった演算命令やCMPといった比較命令の結果により、各フラグが変化します。
それらのフラグの変化の結果をみて、ジャンプするのが条件ジャンプです。
先にJNZだけみましたので、他の条件ジャンプのうち、符号無しデータの比較の場合の条件ジャンプを
まとめてみます。
JZ | jump if zero ZFがセット(=1)の時ジャンプ すなわち、演算の結果が0、比較の結果等しい時にジャンプ |
JB | jump if below CF=1の時ジャンプ すなわち、比較の結果が小の時ジャンプ |
JA | jump if above CF=0かつZF=0の時ジャンプ すなわち、比較の結果が大の時ジャンプ |
JBE | jump if below or equal CF=1またはZF=0の時ジャンプ 比較の結果等しいか小さい時ジャンプ |
JAE | jump if above or equal CF=0の時ジャンプ 比較の結果大きいか等しい時ジャンプ |
符号ありデータ同士の比較の結果の場合の条件ジャンプもあります。下記の通りです
JL | jump if less 比較の結果が小ならジャンプ |
JG | jump if greater 比較の結果が大ならジャンプ |
符号のあり、無し含めて、いろんな条件ジャンプ命令があって、ややこしいとこですが、
一度、フラグとの関係を含めて考えてみて下さい。
(4) プログラムについて若干の説明
”A”のキーが押された場合について、プログラムの流れを説明します。
ALレジスタには”A”のアスキーコード 41 がセットされています
上位桁の取得(上位4bitの取得)
25CD:0109 MOV CL,04
25CD:010B MOV DL,AL
25CD:010D SHR DL,CL
ALの値(=41H)をDLに入れ、 SHR DL, CL でDLを4bit右シフト
41H= 01000001 −−−−> 00000100 =4 上位桁の4を取得
下位桁(下位4bit)の取得
25CD:0112 MOV DL,AL
25CD:0114 AND DL,0F
ALの値(=41H)をDLに入れ、AND DL, 0F で上位4bitをマスクし、下位4bitを取得する
41H = 01000001
AND 0F 00001111
00000001 = 1
下位桁の取得
画面表示ルーチン
アスキーコード表をよく見て下さい。
数字の0から9はアスキーコードと30Hづつちがいます。
またAからFまではアスキーコードと37Hづつ違います。
これを利用して CMP DL, 9 で0から9までと、AからFまでの処理先を変え、
0から9までの表示は ADD DL, 30 で30H加算し
AからFまでの表示は ADD DL, 37 で37H加算してます。
なお、NOPは何もしない命令です。本来、特に必要ありません。愛敬でつけました。
25CD:0123 CMP DL,09
25CD:0126 JG 012E
25CD:0128 ADD DL,30
25CD:012B JMP 0131
25CD:012D NOP
25CD:012E ADD DL,37
25CD:0131 PUSH AX
25CD:0132 MOV AH,02
25CD:0134 INT 21
25CD:0136 POP AX
25CD:0137 RET
ファンクションコール1番
これは1文字入力ですが、リターンキーを押して入力が確定されます。
BASICのINPUTに近いです。
(5) おまけのBASICぷろぐらむ
同様のプログラムをBASICで作成。BASICだと超簡単。
10 Input a$
20 print asc(a$)
30 goto 10
ということで、DEBUGように2題サンプルを作成してみました。知ってる人には退屈な内容だったと思います
が、始めてアセンブラ、機械語に触れる人にはちょっと盛りだくさんで消化不良になりそうなボリューム
だったかもしれません。説明も最小限しかしておりませんし。
機械語はまったく初めてという人にとっては、数時間かけて勉強するくらいのボリュームをつめこみましたので、
DEBUGで実験しながら、ゆっくり理解してください。